<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>执行上下文</title>
</head>

<body>
    <script>
        /**
         * 执行上下文与执行上下文栈
         * 1. 全局执行上下文:只有一个，浏览器中的全局对象就是window对象，this指向这个全局对象
         * 在执行全局代码前将window确定为全局执行上下文
            对全局数据进行预处理
                var定义的全局变量 ===> undefined，添加为window属性
                function声明的全局函数 ===> 赋值（fun），添加为window的方法
                this ===> 赋值为window
        */
        // console.log(fn) //fn 以函数形式声明,函数提升
        // console.log(fn2) //undefined 以变量形式定义，变量提升，函数未提升
        // console.log(a) //undefined

        // var a = 2
        // function fn() {
        //     console.log('fn')
        // }
        // var fn2 = function () {
        //     console.log(fn2)
        // }
        /**
          * 执行上下文与执行上下文栈
          * 2. 函数执行上下文:无数个，只有在函数被调用时才会被创建（这是重点，函数调用才创建函数执行上下文），每次调用函数就会创建一个新的执行上下文，会创建一个私有作用域，函数内部声明的任何变量都不能在当前函数作用外部直接访问
          *  在调用函数，准备执行函数体之前，创建对应的函数执行上下文对象
             对局部数据进行预处理
                 形参变量 ====> 赋值（实参数据） ==> 添加为执行上下文的属性
                 arguments ====> 赋值（实参列表），添加为执行上下文的属性
                 var定义的局部变量 ===> undefined，添加为执行上下文的属性
                 function声明的函数 ===> 赋值（fun），添加为执行上下文的属性
                 this ===> 赋值（调用函数的对象）
             开始执行函数体代码
         */
        /**
          * 执行上下文与执行上下文栈
          * 3. Eval函数执行上下文：指运行在eval函数中的代码，很少用并不建议使用
         */

        /**
         * 执行上下文与执行上下文栈
         * 4. 执行上下文栈
         *  在全局代码执行前，JS引擎就会创建一个栈来存储管理所有的执行上下文对象
            在全局执行上下文（window）确定后，将其添加到栈中（压栈）
            在函数执行上下文创建后，将其添加到栈中（压栈）
            在当前函数执行完后，将栈顶的对象移除（出栈）
            当所有的代码执行完后，栈中只剩下window
        */

        /**
         * *1.生命周期
                执行上下文的生命周期包括三个阶段：创建阶段→执行阶段→回收阶段

            2.创建阶段
                创建阶段即当函数被调用，但未执行任何其内部代码之前

            3.创建流程：

                确定this的值，也被称为this Binding。所以this的值是在执行的时候才能确认，定义的时候不能确认
                词法环境被创建（LexicalEnvironment）
                变量环境被创建（VariableEnvironment）
                ExecutionContext = {  
                ThisBinding = <this value>,     // 确定this 
                LexicalEnvironment = { ... },   // 词法环境
                VariableEnvironment = { ... },  // 变量环境
                }
            4.词法环境
                全局环境：是一个没有外部环境的词法环境，其外部环境引用为null，有一个全局对象，this的值指向这个全局对象
                函数环境：用户在函数中定义的变量被存储在环境记录中，包含了arguments对象，外部环境的引用可以是全局环境，也可以是包含内部函数的外部函数环境
                变量环境：也是一个词法环境，词法环境和变量环境的区别在于前者存储函数声明和变量是由let和const绑定，而变量环境存储环境仅用于var绑定
         * */
        // let a = 20;
        // const b = 30;
        // var c;

        // function multiply(e, f) {
        //     var g = 20;
        //     return e * f * g;
        // }

        // c = multiply(20, 30);

        //执行上下文如下：注意：let和const定义的变量a和b在创建阶段没有被赋值(uninitialized(未初始化状态))，但var声明的变量从在创建阶段被赋值为undefined，这就是变量提升
        /*
            GlobalExectionContext = {

                ThisBinding: <Global Object>,

                LexicalEnvironment: {  // 词法环境
                EnvironmentRecord: {  
                    Type: "Object",  
                    // 标识符绑定在这里  
                    a: < uninitialized >,  
                    b: < uninitialized >,  
                    multiply: < func >  
                }  
                outer: <null>  
                },

                VariableEnvironment: {  // 变量环境
                EnvironmentRecord: {  
                    Type: "Object",  
                    // 标识符绑定在这里  
                    c: undefined,  
                }  
                outer: <null>  
                }  
                }

                FunctionExectionContext = {  
                
                ThisBinding: <Global Object>,

                LexicalEnvironment: {  
                EnvironmentRecord: {  
                    Type: "Declarative",  
                    // 标识符绑定在这里  
                    Arguments: {0: 20, 1: 30, length: 2},  
                },  
                outer: <GlobalLexicalEnvironment>  
                },

                VariableEnvironment: {  
                EnvironmentRecord: {  
                    Type: "Declarative",  
                    // 标识符绑定在这里  
                    g: undefined  
                },  
                outer: <GlobalLexicalEnvironment>  
                }  
            }

        */
        /**
         * 5.执行阶段
             在执行阶段，执行变量赋值、代码执行，如果JS引擎在源代码声明的实际位置找不到变量的值，那么将为其分配·undefined·值
 
           6.回收阶段
             执行上下文出栈等待虚拟机回收执行上下文
        */
        /**
         * 7.执行栈
         * 执行栈也叫调用栈，具有后进先出的结构，
         * 用于存储在代码执行期间创建的所有执行上下文。
         * 当JS引擎开始执行第一行脚本代码时，就会创建一个全局执行上下文然后压入执行栈中，
         * 每当引擎调用一个函数时，就会创建一个函数执行上下文，
         * 然后将这个执行上下文压入到执行栈。引擎会执行位于执行栈栈顶的执行上下文（一般是函数执行上下文），
         * 当该函数执行结束后，对应的执行上下文就会被弹出，然后控制流程就会到达执行栈的下一个执行上下文
            例子如下：
            图片为执行上下文2
        */
        // let a = 'Hello World!';
        // function first() {
        // console.log('Inside first function');
        // second();
        // console.log('Again inside first function');
        // }
        // function second() {
        // console.log('Inside second function');
        // }
        // first();
        // console.log('Inside Global Execution Context');
        /**
         * 流程分析：

            创建全局上下文请压入执行栈
            first函数被调用，创建函数执行上下文并压入栈
            执行first函数过程遇到second函数，再创建一个函数执行上下文并压入栈
            second函数执行完毕，对应的函数执行上下文被推出执行栈，执行下一个执行上下文first函数
            first函数执行完毕，对应的函数执行上下文也被推出栈中，然后执行全局上下文
            所有代码执行完毕，全局上下文也会被推出栈中，程序结束
        */


        /*
            测试题1:  先执行变量提升, 再执行函数提升
        */
        // function a() {}
        // var a
        // console.log(typeof a) // 'function'
        /*
        测试题2:
        */
        // console.log(b in window); // true
        // if (!(b in window)) {
        //     var b = 1
        // }
        // console.log(b) // undefined(说明b在window中，但是没有赋值)--变量提升
        /*
        测试题3: 
            1.此时先进行变量提升var c,然后进行 function c(){}提升
            2.进行c赋值=1
            3.c不是函数
        */
        // var c = 1

        // function c(c) {
        //     console.log(c)
        //     var c = 3 //与此行无关
        // }
        // console.log(c);
        // c(2) // 报错  c is not a function
        /*测试题4: 
            1.此时先进行变量提升var c,然后进行 function c(){}提升
            2.进行c赋值=1
            3.c不是函数
        */

        // function d(c) {
        //     console.log(c)//2
        //     var c = 3 //与此行无关
        // }

        // d(2)
        // console.log(c)// 报错c is not defined
        /**
         * 测试5
        */
        console.log('global begin:' + i)//undefind
        var i = 1;
        foo(1);
        function foo(i) {
            if (i == 4) {
                return;
            }
            console.log('foo() begin:' + i);// 1,2,3
            foo(i + 1);
            console.log('foo() end:' + i);// 3,2,1
        }
        console.log('global end :' + i);//1
    </script>
</body>

</html>